package com.tmt.im.wallet.service;

import com.tmt.im.common.enums.WalletEventType;
import com.tmt.im.common.pojo.IMUserPoJo;
import com.tmt.im.common.pojo.RechargeHistoryPoJo;
import com.tmt.im.common.pojo.WalletChangeDetailPoJo;
import com.tmt.im.common.pojo.WalletPoJo;
import com.tmt.im.jpa.entity.SystemParam;
import com.tmt.im.jpa.repository.SystemParamRepo;
import com.tmt.im.wallet.config.beans.CurrentServiceProperties;
import com.tmt.im.wallet.config.beans.PayProperties;
import com.tmt.im.wallet.controler.RechargeController;
import com.tmt.im.wallet.controler.vo.ApplyRechargeRequest;
import com.tmt.im.wallet.controler.vo.ConfirmRechargeRequest;
import com.tmt.im.wallet.helper.IgniteQueryHelper;
import com.tmt.im.wallet.helper.PrincipalHelper;
import com.tmt.im.wallet.helper.RechargeHelper;
import com.tmt.springboot.common.helper.IdSnowFlake;
import com.tmt.springboot.common.restful.ExecuteResult;
import com.tmt.springboot.common.restful.IExecuteResult;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteSemaphore;
import org.apache.ignite.transactions.Transaction;
import org.apache.ignite.transactions.TransactionConcurrency;
import org.apache.ignite.transactions.TransactionIsolation;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.client.RestTemplate;

import java.io.BufferedOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.Principal;
import java.sql.Timestamp;
import java.text.MessageFormat;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @Description TODO
 * @Author wuyi
 * @Date 2024/10/20 14:07
 * @Version 1.0
 **/
@Service
@Slf4j
public class RechargeHistoryService extends BaseService {

    private final ApplicationContext applicationContext;

    private final String apiUrlPrefix;

    private final PayProperties payProperties;

    private final CurrentServiceProperties serviceProperties;

    private final RestTemplate restTemplate;

    private final SystemParamRepo systemParamRepo;


    public RechargeHistoryService(ApplicationContext applicationContext,
                                  Ignite ignite,
                                  @Value("${api-url-prefix}")
                                  String apiUrlPrefix,
                                  PayProperties payProperties,
                                  CurrentServiceProperties serviceProperties,
                                  @Qualifier("myRestTemplate")
                                  RestTemplate restTemplate,
                                  SystemParamRepo systemParamRepo) {
        super(ignite);
        this.applicationContext = applicationContext;
        this.apiUrlPrefix = apiUrlPrefix;
        this.payProperties = payProperties;
        this.serviceProperties = serviceProperties;
        this.restTemplate = restTemplate;
        this.systemParamRepo = systemParamRepo;
    }

    /**
     * 申请充值
     *
     * @param request
     * @param principal
     * @return
     */
    public IExecuteResult<String> applyRecharge(ApplyRechargeRequest request, Principal principal) {
        Long currentUserId = PrincipalHelper.getCurrentUserId(principal);
        IMUserPoJo currentUser = userCache.get(currentUserId);
        assert currentUser != null;

        //检查是否符合最小充值额度的要求。
        Long minValue = null;
        try {
            SystemParam param = systemParamRepo.findOneByParamCode("recharge-min-amount");
            minValue = Long.parseLong(param.getParamValue());
            if (minValue < 100) {
                return ExecuteResult.failure("最小充值额度配置参数的值不能小于1元", null);
            }
        } catch (Exception ex) {
            return ExecuteResult.failure("读取最小充值额度配置参数失败", null);
        }

        if (request.getAmount() < minValue) {
            long yuanValue = minValue / 100;
            return ExecuteResult.failure(MessageFormat.format("充值额度不能少于{0}元", "" + yuanValue), null);
        }

        String orderNo = UUID.randomUUID().toString().replace("-", "");
        String url = getNotifyUrlPrefix();

        RechargeHistoryPoJo history = RechargeHistoryPoJo.builder()
                .id(IdSnowFlake.getId())
                .merchantNum(payProperties.getMerchantAccount())
                .orderNo(orderNo)
                .amount(request.getAmount())
                .notifyUrl(url + "/async")
                .returnUrl(url + "/sync")
                .attch(null)
                .channelCode(request.getType().getType())
                .userId("" + currentUserId)
                .userNick(currentUser.getNickname())
                .sign(null)
                .confirmMode(0)
                .version(0L)
                .createTime(new Timestamp(System.currentTimeMillis()))
                .insert(true)
                .build();

        StringBuilder error = new StringBuilder();
        if (RechargeHelper.applyRecharge(history, restTemplate, payProperties, error)) {
            rechargeHistoryCache.put(history.getKey(), history);
            return ExecuteResult.success("操作成功", history.getPayUrl());
        }

        return ExecuteResult.failure(error.toString(), null);
    }

    public void syncConfirmRecharge(HttpServletRequest request, HttpServletResponse response) {
        String paySuccessPic = "/images/pay-success.png";
        InputStream inputStream = RechargeHistoryService.class.getResourceAsStream(paySuccessPic);
        if (inputStream != null) {
            try {
                // 清空response
                response.reset();
                // 设置response的Header
                response.setCharacterEncoding("UTF-8");
                //Content-Disposition的作用：告知浏览器以何种方式显示响应返回的文件，用浏览器打开还是以附件的形式下载到本地保存
                //attachment表示以附件方式下载 inline表示在线打开 "Content-Disposition: inline; filename=文件名.mp3"
                // filename表示文件的默认名称，因为网络传输只支持URL编码的相关支付，因此需要将文件名URL编码后进行传输,前端收到后需要反编码才能获取到真正的名称
                String filename = URLEncoder.encode("pay-success.png", StandardCharsets.UTF_8);
                response.addHeader("Content-Disposition", "inline;filename=" + filename);
                response.setContentType(MediaType.IMAGE_PNG_VALUE);
                // 告知浏览器文件的大小
                int len = 0;
                int total = 0;
                byte[] content = new byte[10 * 1024];
                try (OutputStream output = new BufferedOutputStream(response.getOutputStream())) {
                    do {
                        len = inputStream.read(content);
                        if (len > 0) {
                            output.write(content, 0, len);
                            output.flush();
                            total += len;
                        }
                    } while (len > 0);
                } catch (Exception ignored) {
                }
                response.addHeader("Content-Length", "" + total);
            } catch (Exception ex) {
                ex.printStackTrace();
            } finally {
                try {
                    inputStream.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }

    /**
     * 确认充值
     *
     * @param httpServletRequest
     * @return
     */
    public Long asyncConfirmRecharge(HttpServletRequest httpServletRequest) {
        ConfirmRechargeRequest request = convertFrom(httpServletRequest);

        String sql = MessageFormat.format("select * from {0} where merchantNum = ? and orderNo = ? and platformOrderNo = ?",
                RechargeHistoryPoJo.CACHE_NAME);
        RechargeHistoryPoJo history = IgniteQueryHelper.findOneBy(sql, new Object[]{request.getMerchantNum(),
                request.getOrderNo(), request.getPlatformOrderNo()}, RechargeHistoryPoJo.class, rechargeHistoryCache);
        if (history == null || history.getConfirmMode() != 0) {
            return null;
        }

        //核对签名。
        String strSign = request.getState() + request.getOrderNo() + request.getAmount() +
                history.getNotifyUrl() + payProperties.getMerchantSecretKey();
        String sign = DigestUtils.md5Hex(strSign);
        if (!request.getSign().equals(sign)) {
            log.error("----asyncConfirmRecharge------{}:{}", "签名不一致", strSign);
            return null;
        }

        try (IgniteSemaphore rechargeSemaphore = ignite.semaphore("confirmRecharge:" + history.getId(), 1, true, true)) {
            if (rechargeSemaphore.tryAcquire(3, TimeUnit.SECONDS)) {
                //再次获取充值历史记录。
                history = IgniteQueryHelper.findOneBy(sql, new Object[]{request.getMerchantNum(),
                        request.getOrderNo(), request.getPlatformOrderNo()}, RechargeHistoryPoJo.class, rechargeHistoryCache);
                if (history == null || history.getConfirmMode() != 0) {
                    return null;
                }

                BigDecimal amount = new BigDecimal(request.getActualPayAmount());
                long actualAmount = amount.multiply(BigDecimal.valueOf(100L)).longValue();

                //回填充值记录上的值。
                history.setConfirmMode(1);
                history.setState(Integer.parseInt(request.getState()));
                history.setActualPayAmount(actualAmount);
                history.setPayTime(request.getPayTime());
                history.setPayChannelCode(request.getChannelCode());
                //保存数据。
                rechargeHistoryCache.put(history.getKey(), history);
            } else {
                log.error("获取全局信号量失败");
            }
        } catch (Exception ex) {
            log.error("---创建全局信号量失败----{}", ex.getMessage());
        }
        return history.getId();
    }

    public void updateWalletBalance(Long rechargeHistoryId) {
        RechargeHistoryPoJo history = rechargeHistoryCache.get(rechargeHistoryId);
        if (history == null || Boolean.TRUE.equals(history.getSyncBalance()) || history.getConfirmMode() == 0 ||
                (history.getConfirmMode() == 1 && history.getState() != 1) ||
                (history.getConfirmMode() == 2 && history.getState() != 4)) {
            return;
        }

        IMUserPoJo userPoJo = userCache.get(Long.parseLong(history.getUserId()));
        assert userPoJo != null;

        try (IgniteSemaphore walletSemaphore = ignite.semaphore("wallet:" + userPoJo.getWalletId(), 1, true, true)) {
            if (walletSemaphore.tryAcquire(3, TimeUnit.SECONDS)) {

                //再获取一次充值历史记录。
                history = rechargeHistoryCache.get(rechargeHistoryId);
                if (history == null || Boolean.TRUE.equals(history.getSyncBalance()) || history.getConfirmMode() == 0 ||
                        (history.getConfirmMode() == 1 && history.getState() != 1) ||
                        (history.getConfirmMode() == 2 && history.getState() != 4)) {
                    return;
                }

                String sql = MessageFormat.format("select * from {0} where eventId = ? and eventType = ?", WalletChangeDetailPoJo.CACHE_NAME);
                WalletChangeDetailPoJo changeDetailPoJo = IgniteQueryHelper.findOneBy(sql,
                        new Object[]{history.getId(), WalletEventType.RECHARGE.name()}, WalletChangeDetailPoJo.class, walletDetailCache);
                if (changeDetailPoJo != null) {
                    //已经充值过了，则直接返回。
                    return;
                }

                try (Transaction tx = ignite.transactions().txStart(TransactionConcurrency.OPTIMISTIC,
                        TransactionIsolation.SERIALIZABLE)) {
                    WalletPoJo wallet = walletCache.get(userPoJo.getWalletId());
                    assert wallet != null;

                    //直接累加到余额字段上。
                    wallet.setBalance(wallet.getBalance() + history.getActualPayAmount());

                    //创建钱包余额变动记录。
                    WalletChangeDetailPoJo changeDetail = WalletChangeDetailPoJo.builder()
                            .id(IdSnowFlake.getId())
                            .eventId(history.getId())
                            .eventType(WalletEventType.RECHARGE.name())
                            .amount(history.getActualPayAmount())
                            .balance(wallet.getBalance())
                            .walletId(wallet.getId())
                            .creatorId(userPoJo.getId())
                            .version(0L)
                            .createTime(new Timestamp(System.currentTimeMillis()))
                            .insert(true)
                            .build();

                    history.setSyncBalance(true);

                    walletCache.put(wallet.getKey(), wallet);
                    walletDetailCache.put(changeDetail.getKey(), changeDetail);
                    rechargeHistoryCache.put(history.getKey(), history);
                    tx.commit();
                }
            } else {
                log.error("获取全局信号量失败");
            }
        } catch (Exception ex) {
            log.error("---创建全局信号量失败----{}", ex.getMessage());
        }
    }


    /**
     * 查询充值结果
     *
     * @param rechargeId
     * @return
     */
    public boolean queryRechargeResult(Long rechargeId, StringBuilder error) {
        RechargeHistoryPoJo historyPoJo = rechargeHistoryCache.get(rechargeId);
        if (RechargeHelper.queryRechargeResult(historyPoJo, restTemplate, payProperties, error)) {
            historyPoJo.setInsert(false);
            rechargeHistoryCache.put(historyPoJo.getKey(), historyPoJo);
            return true;
        }
        return false;
    }

    private String getNotifyUrlPrefix() {
        RechargeController controller = applicationContext.getBean(RechargeController.class);
        RequestMapping ann = null;
        if (controller.getClass().getSimpleName().endsWith(".RechargeController")) {
            ann = controller.getClass().getAnnotation(RequestMapping.class);
        } else {
            ann = ((Class<?>) controller.getClass().getGenericSuperclass()).getAnnotation(RequestMapping.class);
        }
        assert ann != null;

        String uri = ann.value()[0];
        uri = uri.replace("${api-url-prefix}", apiUrlPrefix);
        return "http://" + serviceProperties.getExternalWalletService() + uri + "/confirm";
    }

    private ConfirmRechargeRequest convertFrom(HttpServletRequest req) {
        ConfirmRechargeRequest request = new ConfirmRechargeRequest();
        Field[] fields = request.getClass().getDeclaredFields();
        for (Field field : fields) {
            try {
                String val = req.getParameter(field.getName());
                log.error("---{}={}---", field.getName(), val);
                field.setAccessible(true);
                field.set(request, val);
            } catch (Exception ex) {
            }
        }
        return request;
    }

}
